/**
 * @file
 * Core library implementation.
 *
 * Kisa provides spell checking as you type and displays the result in a small
 * window on your Desktop.
 *
 * Copyright (c) 2009 by Pete Black <theblackpeter@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * @author Pete Black <theblackpeter@gmail.com>
 */
#include <QApplication>
#include <QDBusInterface>
#include <QDBusReply>
#include <QDir>
#include <QFile>
#include <QLocale>
#include <QSettings>
#include <QProcess>
#include <QtDebug>
#include "kisalib.h"
#include "kisalocale.h"

/**
 * The current library version.
 */
#ifndef KISALIB_VERSION
#define KISALIB_VERSION "0.63"
#endif

/**
 * Instances of this class will have this name.
 * @note this is not a unique identifier, just a common name.
 */
#ifndef OBJECT_NAME
#define OBJECT_NAME "kisalib"
#endif

/**
 * Name used when outputting debug information.
 */
#ifndef OBJECT_DEBUG_NAME
#define OBJECT_DEBUG_NAME "kisalib:"
#endif

/**
 * The icon "actions" path.
 */
#ifndef ICON_ACTION_PATH
#define ICON_ACTION_PATH "/16x16/actions"
#endif

/**
 * The icon "apps" path.
 */
#ifndef ICON_APPS_PATH
#define ICON_APPS_PATH "/16x16/apps"
#endif

/**
 * The default (KDE 4) icon theme.
 */
#ifndef DEFAULT_ICON_THEME
#define DEFAULT_ICON_THEME "oxygen"
#endif

/**
 * When no keyboard locale is found default to this.
 */
#ifndef DEFAULT_KEYBOARD_LOCALE
#define DEFAULT_KEYBOARD_LOCALE QLocale::system().name()
#endif

/**
 * Set to 1 to enable Kxkb D-Bus support to overcome issues with
 * getKeyboardInputLayout not returning any layouts.
 *
 * @todo remove when not needed
 */
#ifndef ENABLE_KXKB_SUPPORT
#define ENABLE_KXKB_SUPPORT 1
#endif

/**
 * The KDE config command used to query for settings and installation paths.
 */
#ifndef KDE_CONFIG_COMMAND
#define KDE_CONFIG_COMMAND "kde4-config"
#endif

/**
 * Path to where extra application icons end up.
 */
#ifndef KISA_ICONS_PATH
#define KISA_ICONS_PATH "share/kisa/icons"
#endif

/**
 * The KDE global configuration file that holds the users icon theme.
 */
#ifndef KDE_GLOBALS
#define KDE_GLOBALS "/.kde/share/config/kdeglobals"
#endif

/**
 * Path to the localization folder used as a root for flag icons.
 */
#ifndef LOCALE_PATH
#define LOCALE_PATH "/usr/share/locale/l10n"
#endif

KisaLib* KisaLib::m_instance = NULL;

KisaLib::KisaLib(const QString& displayString) :
  AspellLib(), X11Lib(displayString) {
  qDebug() << OBJECT_DEBUG_NAME << "using KisaLib version" << KISALIB_VERSION;

  initMembers();

  qDebug() << OBJECT_DEBUG_NAME << OBJECT_NAME << "created";
}

KisaLib::~KisaLib() {
  qDebug() << OBJECT_DEBUG_NAME << OBJECT_NAME << "destroyed";
}

QString KisaLib::getCountryFlagURL(QString countryCode) {
  // make sure we have the right format
  if(countryCode.size() != 2) {
    qDebug() << OBJECT_DEBUG_NAME << countryCode << "is invalid country code format";
    return "";
  }
  // just in case...
  countryCode = countryCode.toLower();

  QString url;

  // get flags paths from resources
  QStringList searchPaths = QDir::searchPaths("flags");
  if(!searchPaths.isEmpty())
    url = searchPaths.at(0);
  else
    return "";

  // try with country code as a sub directory first
  if(QFile::exists(url + countryCode + "/flag.png"))
    // create the full url using the resource "flags" directory location
    url += countryCode + "/flag.png";
  else
    url += countryCode + ".png";

  if(QFile::exists(url))
    return url;
  else
    qDebug() << OBJECT_DEBUG_NAME << "unable to find flag file for country " << countryCode << " in " << url;

  return "";
}

QString KisaLib::getKeyboardInputLayout() {
  // first try to get the current keyboard layout from the system instead
  QString keyboardLayout = QApplication::keyboardInputLocale().name();

  // the "C" locale is identical to English/UnitedStates (en_US) and is used as
  // a default or if an error occurred when creating the QLocale()
  if(keyboardLayout.isEmpty() || keyboardLayout == "C") {
    qDebug() << OBJECT_DEBUG_NAME << "system returned default locale, trying XKb";

    // get the keyboard layout from XKb and guess the full locale code
    keyboardLayout = getKeyboardLayoutMap();

    if(!keyboardLayout.isEmpty()) {
      qDebug() << OBJECT_DEBUG_NAME << "got XKb keyboard map" << keyboardLayout;
      keyboardLayout = guessKeyboardLayoutFromMap(keyboardLayout);
    }

#if ENABLE_KXKB_SUPPORT
    if(keyboardLayout.isEmpty()) {
      qDebug() << OBJECT_DEBUG_NAME << "could not get keyboard map from XKb, trying calling Kxkb over D-Bus";

      keyboardLayout = getKeyboardInputLayoutKxkb();
      if(!keyboardLayout.isEmpty())
        keyboardLayout = guessKeyboardLayoutFromMap(keyboardLayout);

      if(keyboardLayout.isEmpty() || keyboardLayout == "C") {
        keyboardLayout = DEFAULT_KEYBOARD_LOCALE;
        qDebug() << OBJECT_DEBUG_NAME << "could not get keyboard layout locale from Kxkb, using default locale" << DEFAULT_KEYBOARD_LOCALE;
      }
#else
      keyboardLayout = DEFAULT_KEYBOARD_LOCALE;
      qDebug() << OBJECT_DEBUG_NAME << "could not get keyboard layout, using default locale" << DEFAULT_KEYBOARD_LOCALE;
      qDebug() << OBJECT_DEBUG_NAME << "perhaps you want to add your current layout to the override list" << keyboardLayout;
#endif
    }
  }
  else {
      qDebug() << OBJECT_DEBUG_NAME << "got keyboard layout" << keyboardLayout << "from Qt";
  }

  return keyboardLayout;
}

QString KisaLib::getKeyboardInputLayoutKxkb() {
  QString keyboardLayout;

  QDBusInterface kxkb("org.kde.kxkb", "/kxkb", "org.kde.KXKB");
  QDBusReply<QString> reply = kxkb.call("getCurrentLayout");

  if(reply.isValid()) {
    // keyboard codes and variants are on the form of countrycode(variant)
    keyboardLayout = reply.value();
    qDebug() << OBJECT_DEBUG_NAME << "got keyboard layout from Kxkb" << keyboardLayout;
  }
  else {
    qDebug() << OBJECT_DEBUG_NAME << "got no response from Kxkb";
  }

  return keyboardLayout;
}

const QString KisaLib::guessLocale(const QString& country) {
  KisaLocale kisaLocale;
  return kisaLocale.guessLocaleFromCountryCode(country).name();
}

void KisaLib::initialize(const QString& iconspath,
                         const QString& flagspath,
                         const QString& dictionary,
                         const QString& dictDir,
                         const QString& aspellPrefix,
                         const QString& extraDicts) {
  if(!m_instance)
    m_instance = new KisaLib();

  // set the search paths for where to look for icons
  setIconsSearchPaths(iconspath);

  // set the search path for where to look for flag icons
  setFlagsSearchPaths(flagspath);

  QString languageCode = dictionary;

  if(languageCode.isEmpty()) {
    qDebug() << OBJECT_DEBUG_NAME << "no language given, getting language from keyboard layout";

    // get the current input language from keyboard layout
    // this is usually a 5 character string in the form country_LANGUAGE
    // but sometimes we only get the two-letter ISO 639 country code
    languageCode = getKeyboardInputLayout();

    // default to system locale
    if(languageCode.isEmpty()) {
      qDebug() << OBJECT_DEBUG_NAME << "no language code from keyboard layout found, defaulting to system layout";
      languageCode = QLocale::system().name();
    }
  }
  // create the Aspell spell checker and load dictionaries
  AspellLib::initialize(languageCode, dictDir, aspellPrefix, extraDicts);
}

void KisaLib::initMembers() {
  // init base class members
  X11Lib::initMembers();
  AspellLib::initMembers();
}

KisaLib* KisaLib::instance(const QString& displayString) {
  if(!m_instance)
    m_instance = new KisaLib(displayString);

  return m_instance;
}

const QString KisaLib::guessKeyboardLayoutFromMap(const QString& keyboardLayoutMap) {
  QString keyboardLocaleCode;

  // check if we have an override already defined
  KisaLocale kisaLocale;
  QLocale locale = kisaLocale.getLocaleFromKeyboardLayout(keyboardLayoutMap);

  if(locale != QLocale::c()) {
    keyboardLocaleCode = locale.name();
    qDebug() << OBJECT_DEBUG_NAME << "got language locale from override list" << keyboardLocaleCode;
    return keyboardLocaleCode;
  }

  QString countryCode;
  QString variant;

  // check if we have a variant
  if(keyboardLayoutMap.contains("(")) {
    countryCode = keyboardLayoutMap.split("(").first();
    countryCode = countryCode.split("_").first();
    variant = keyboardLayoutMap.split("(").last();
    // remove the last ")"
    variant.chop(1);
    qDebug() << OBJECT_DEBUG_NAME << "using country code" << countryCode << "and variant" << variant;

    // check if the variant is a language
    if(kisaLocale.guessLocaleFromLanguageCode(variant) != QLocale::c()) {
      keyboardLocaleCode = variant + "_" + countryCode.toUpper();
      qDebug() << OBJECT_DEBUG_NAME << "found language" << variant;
    }
  }
  // no variant, guess language instead
  else {
    countryCode = keyboardLayoutMap;
    countryCode = countryCode.split("_").first();
    qDebug() << OBJECT_DEBUG_NAME << "no language found, guessing language using country code" << countryCode;

    QString language = kisaLocale.getLanguageFromCountryCode(countryCode);

    if(!language.isEmpty()) {
      keyboardLocaleCode = language + "_" + countryCode.toUpper();
      qDebug() << OBJECT_DEBUG_NAME << "got language" << language;
    }
    else {
      keyboardLocaleCode = DEFAULT_KEYBOARD_LOCALE;
      qDebug() << OBJECT_DEBUG_NAME << "no language found using default locale" << DEFAULT_KEYBOARD_LOCALE;
    }
  }

  qDebug() << OBJECT_DEBUG_NAME << "got language locale" << keyboardLocaleCode << "from keyboard layout map" << keyboardLayoutMap;

  return keyboardLocaleCode;
}

void KisaLib::setFlagsSearchPaths(QString path) {
  // check if the given icons directory actually exists
  if(path.isEmpty() || !QDir(path).exists()) {

    // try the default locale path first, used by most distros
    path = LOCALE_PATH;

    // try getting the KDE locale path
    if(!QDir(path).exists()) {
      QProcess process;
      process.start(QString(KDE_CONFIG_COMMAND) + " --path locale");
      QString output;

      // block for a maximum of 5 s
      if(process.waitForFinished(5000))
        output = process.readAllStandardOutput().simplified();

      // grab the last path
      QStringList kdeLocalePaths = output.split(":");
      path = kdeLocalePaths.last() + "l10n/";
    }
  }

  if(QDir(path).exists()) {
    if(!path.endsWith("/"))
      path += "/";

    QDir::addSearchPath("flags", path);
  }
  else
    qWarning() << OBJECT_NAME << "search path for flags:" << path << "doesn't exist";

  qDebug() << OBJECT_DEBUG_NAME << "using search paths for flags:" << QDir::searchPaths("flags");
}

void KisaLib::setIconsSearchPaths(QString path) {
  // check if the given icons directory actually exists
  if(!QDir(path).exists()) {
    path.clear();
    qDebug() << OBJECT_DEBUG_NAME << "unable to find icons directory" << path;
  }

  // if no command line argument was given, skip it
  if(!path.isEmpty())
    QDir::addSearchPath("icons", path);

  // use as settings object to get the current KDE icons theme located under
  // the group [Icons] with value "Theme in" the kdeglobals config file
  // make sure QSettings dosn't creates a missing file
  QString kdeGlobals = QDir::homePath() + KDE_GLOBALS;

  if(QFile().exists(kdeGlobals)) {
    QSettings kdeSettings(kdeGlobals, QSettings::IniFormat);
    kdeSettings.beginGroup("Icons");
    QString theme = kdeSettings.value("Theme").toString();
    kdeSettings.endGroup();

    // kde4 has oxygen as default theme, but the setting is not always there
    if(theme.isEmpty())
      theme = DEFAULT_ICON_THEME;

    // now get the icons path
    QProcess process;
    process.start(QString(KDE_CONFIG_COMMAND) + " --path icon");
    QString output;

    // block for a maximum of 5 s
    if(process.waitForFinished(5000))
      output = process.readAllStandardOutput().simplified();

    QStringList kdePaths = output.split(":");

    // finally try to find the full path of the icons theme
    foreach(QString kdePath, kdePaths)
        if(QDir(kdePath + theme).exists())
          path = kdePath + theme;
  }

  /// NOTE the order in which the pats are added matters
  if(QDir(path + ICON_ACTION_PATH).exists())
    QDir::addSearchPath("icons", path + ICON_ACTION_PATH);

  if(QDir(path + ICON_APPS_PATH).exists())
    QDir::addSearchPath("icons", path + ICON_APPS_PATH);

  // add a common default paths just in case
  path = "/usr/share/icons/" + QString(DEFAULT_ICON_THEME);
  if(QDir(path + ICON_ACTION_PATH).exists())
    QDir::addSearchPath("icons", path + ICON_ACTION_PATH);

  if(QDir(path + ICON_APPS_PATH).exists())
    QDir::addSearchPath("icons", path + ICON_APPS_PATH);

  // add the application icon path as well
  QString iconsPath = QCoreApplication::applicationDirPath();

  // remove the "bin" folder at the end and add the application icons path
  iconsPath.chop(3);

  iconsPath += KISA_ICONS_PATH;
  QDir::addSearchPath("icons", iconsPath);

  qDebug() << OBJECT_DEBUG_NAME << "using search paths for icons:" << QDir::searchPaths("icons");
}
